#include "kernel.cuh"
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include "sparseMatrix.cuh"
#include "stdio.h"

int sparseMatrix_t::comparator(int a, int b, sortFlag flag){
	if(flag == byRow){
		if(row[a] > row[b])return 1;
		if(row[a] < row[b])return -1;
		if(row[a] == row[b]){
			if(col[a] > col[b])return 1;
			if(col[a] < col[b])return -1;
			if(col[a] == col[b])return 0;
		}
	}
	if(flag ==byCol){
		if(col[a] > col[b])return 1;
		if(col[a] < col[b])return -1;
		if(col[a] == col[b]){
			if(row[a] > row[b])return 1;
			if(row[a] < row[b])return -1;
			if(row[a] == row[b])return 0;
		}
	}
}
/*el_a = a->index[i];
			el_b = b->index[j];
			temp=0.0;
			
			while(a->col[el_a] == i && b->row[el_b] == j){
				k_a = a->row[el_a];
				k_b = b->col[el_b];
				
				if(k_a == k_b){
					temp += a->data[el_a] * b->data[el_b];
					el_a++;
					el_b++;
				} else if(k_a > k_b){
					el_b++;
				} else if(k_a < k_b){
					el_a++;
				}
			}
*/

void sparseMatrix_t::printMatrix(sortFlag s){
	int i,j;
	int el;
	if(s == byRow){
		for(i=0;i<numRows;i++){
			el = index[i];
			printf("%d->", el);
			while(row[el] == i){
				printf("[%d,%d] = %g\t", row[el], col[el], data[el]);
				el++;
			}
			printf("\n");
		}
	}
	if(s == byCol){
		for(i=0;i<numCols;i++){
			el = index[i];
			printf("%d->", el);
			while(col[el] == i){
				printf("[%d,%d] = %g\t", row[el], col[el], data[el]);
				el++;
			}
			printf("\n");
		}
	}
	
}

void sparseMatrix_t::allocateData(int numRows, int numCols, int numElements){
	data = new double[numElements];
	row = new int[numElements];
	col = new int[numElements];
	this->numRows = numRows;
	this->numCols = numCols;
	this->numElements = numElements;
	this->index = NULL;
}

void sparseMatrix_t::allocateDataGPU(int numRows, int numCols, int numElements){
	cudaError_t status = cudaSuccess;

	status = cudaMalloc(&data, numElements * sizeof(double));
	logStatus(status, "Memory Allocation");

	status = cudaMalloc(&row, numElements * sizeof(int));
	logStatus(status, "Memory Allocation");

	status = cudaMalloc(&col, numElements * sizeof(int));
	logStatus(status, "Memory Allocation");

	status = cudaMalloc(&index, numCols * sizeof(int));
	logStatus(status, "Memory Allocation");
	
	this->numRows = numRows;
	this->numCols = numCols;
	this->numElements = numElements;
}

sparseMatrix_t* sparseMatrix_t::copyMatrixToGPU(int numRows, int numCols, int numElements){
	sparseMatrix_t *onDevice;
	cudaError_t status = cudaSuccess;
	
	status = cudaMalloc(&onDevice, sizeof(sparseMatrix_t));
	logStatus(status, "Memory Allocation");

	status = cudaMemcpy(onDevice, this, sizeof(sparseMatrix_t), cudaMemcpyHostToDevice);
	logStatus(status, "Memory Copy");

	return onDevice;
}

void sparseMatrix_t::copyDataToGPU(sparseMatrix_t onDevice){
	cudaError_t status = cudaSuccess;

	status = cudaMemcpy(onDevice.data, this->data, this->numElements * sizeof(double), cudaMemcpyHostToDevice);
	logStatus (status, "Memory copy to GPU");

	status = cudaMemcpy(onDevice.row, this->row, this->numElements * sizeof(int), cudaMemcpyHostToDevice);
	logStatus (status, "Memory copy to GPU");

	status = cudaMemcpy(onDevice.col, this->col, this->numElements * sizeof(int), cudaMemcpyHostToDevice);
	logStatus (status, "Memory copy to GPU");

	status = cudaMemcpy(onDevice.index, this->index, this->numCols * sizeof(int), cudaMemcpyHostToDevice);
	logStatus (status, "Memory copy to GPU");
}

sparseMatrix_t* sparseMatrix_t::transferMatrixToGPU(){
	sparseMatrix_t *onHost = new sparseMatrix_t;
	onHost->allocateDataGPU(numRows, numCols, numElements);
	onHost->onDevice = onHost->copyMatrixToGPU(numRows, numCols, numElements);
	copyDataToGPU(*(onHost));
	return onHost;
}

void sparseMatrix_t::copyDataToCPU(sparseMatrix_t onDevice){
	cudaError_t status = cudaSuccess;

	status = cudaMemcpy(this->data, onDevice.data, this->numElements * sizeof(double), cudaMemcpyDeviceToHost);
	logStatus (status, "Memory copy from GPU");

	status = cudaMemcpy(this->row, onDevice.row, this->numElements * sizeof(int), cudaMemcpyDeviceToHost);
	logStatus (status, "Memory copy from GPU");

	status = cudaMemcpy(this->col, onDevice.col, this->numElements * sizeof(int), cudaMemcpyDeviceToHost);
	logStatus (status, "Memory copy from GPU");

	status = cudaMemcpy(this->index, onDevice.index, this->numCols * sizeof(int), cudaMemcpyDeviceToHost);
	logStatus (status, "Memory copy to GPU");
}
void sparseMatrix_t::swap(int a, int b){
	double t;
	int temp;

	t = data[a];
	data[a] = data[b];
	data[b] = t;

	temp = row[a];
	row[a] = row[b];
	row[b] = temp;

	temp = col[a];
	col[a] = col[b];
	col[b] = temp;
}

void sparseMatrix_t::bubbleSort(int low, int high, sortFlag flag){
	int i,j;
	int hasChanged;
	
	
	hasChanged = false;
	for(j=low ; j< high; j++){
		for(i=low; i<high-1; i++){
			if(comparator(i,i+1,flag)==1){
				swap(i,i+1);
				hasChanged = true;
			}
		}
		if(!hasChanged)break;
	}
	
	
}

void sparseMatrix_t::qsort(int low, int high, sortFlag flag){
	int i,j,pivot;
	if(low<high){
		pivot = (high - low)/2;
		i = low - 1;
		for(j = low; j<high; j++){
			if(comparator(j, pivot, flag) != 1){
				i++;
				swap(i,j);
			}
		}
		i++;
		qsort(low, i-1, flag);
		qsort(i+1, high, flag);
	}
}

void sparseMatrix_t::buildIndex(sortFlag flag){
	int i,j;
	if(index != NULL){
		delete[] index;
		index = NULL;
	}
	if(flag == byCol){
		index = new int[numCols];
		j=0;
		for(i=0;i<numElements;i++){
			if(col[i] == j){
				index[j] = i;
				j++;
			}
			if(col[i] > j){
				index[j] = -1;
				j++;
				i--;
			}
		}
		while(j<numCols){
			index[j]=-1;
			j++;
		}
	}
	if(flag == byRow){
		index = new int[numRows];
		j = 0;
		for(i=0;i<numElements;i++){
			if(row[i] == j){
				index[j] = i;
				j++;
			}
			if(row[i] > j){
				index[j] = -1;
				j++;
				i--;
			}
		}
		while(j<numRows){
			index[j]=-1;
			j++;
		}
	}

}

int sparseMatrix_t::nextIndex(int curIndex, sortFlag flag){
	int i,j;
	if(flag == byRow){
		i = curIndex+1;
		while(index[i] == -1 && i< numRows)i++;
		return i;
	}
	if(flag == byCol){
		i = curIndex+1;
		while(index[i] == -1 && i< numCols)i++;
		return i;
	}
	return -1;
}

void sparseMatrix_t::testSort(sortFlag flag){
	int i;
	int x,y;
	x = y = 0;
	if(flag == byRow){
		for(i=0;i<numElements;i++){
			if( y > row[i] ){
				printf("%d, %d >>> %d, %d \n", x, y, col[i], row[i]);
			} else {
				if( x>col[i] && y == row[i] ){
					printf("%d, %d >>> %d, %d \n", x, y, col[i], row[i]);
				}
			}
			x = col[i];
			y = row[i];
		}
	}
	if(flag == byCol){
		for(i=0;i<numElements;i++){
			if( x>col[i] ){
				printf("%d, %d >>> %d, %d \n", x, y, col[i], row[i]);
			} else {
				if( y > row[i] && x == col[i] ){
					printf("%d, %d >>> %d, %d \n", x, y, col[i], row[i]);
				}
			}
			x = col[i];
			y = row[i];
		}
	}

}

void sparseMatrix_t::sort(sortFlag flag){
	int i,j;
	qsort( 0, numElements, flag);
	buildIndex(flag);

}

void sparseMatrix_t::deallocate(){
	delete[] data;
	delete[] row;
	delete[] col;
	if(index != NULL) delete[] index;
	return;
}

void sparseMatrix_t::deallocateGPU(){
	cudaError_t status = cudaSuccess;
	status = cudaFree(data);
	logStatus(status, "Deallocate Memory");
	status = cudaFree(row);
	logStatus(status, "Deallocate Memory");
	status = cudaFree(col);
	logStatus(status, "Deallocate Memory");
	return;
}

